从旧版 Rive Apple 运行时迁移
概述
新的 Rive Apple 运行时是对公共 API 和内部架构的近乎完全重写。虽然从概念上讲大多数操作都有等效项,但两个 API 是不兼容的。任何你想要迁移的旧版 API 中的现有工作都必须使用新 API 重建。
本指南涵盖:
本指南并非详尽无遗,因为那样会与现有通用文档重复。有关特定主题的更多详细信息,请参阅文档的相关章节。
包和导入
新的 Apple 运行时与旧版运行时在同一个 Swift 包和 CocoaPods pod 中可用。两个运行时 API 都在同一个包中,因此你可以使用相同的导入语句导入运行时。
import RiveRuntime
异步 API
新的运行时围绕 Swift Concurrency 构建。大多数设置和查询操作都是异步的,应在 async 上下文中调用。
有关更多信息,请参见 Apple 快速开始指南,了解端到端的设置示例。
常见示例包括:
- 异步创建
Worker - 创建
File和Rive对象 - 创建画板、状态机和视图模型实 例
let worker = try await Worker()
let file = try await File(source: .local("my_file", Bundle.main), worker: worker)
let rive = try await Rive(file: file)
生命周期和线程
旧版运行时实际上通过 RiveViewModel 和 RiveView 在主线程上驱动。
新的运行时引入了 Worker 对象用于后台处理,同时仍然在主 actor 上强制执行 API 调用。在实践中:
Worker拥有后台处理上下文File强引用其Worker- 在 worker 上注册的 带外资源可以在通过该 worker 创建的文件之间共享
有关更多信息,请参见线程部分获取其他详细信息。
对于大多数应用,推荐使用共享 worker:
下面的 AsyncShared 以及 Worker 扩展属于应用层的辅助代码,并不包含在 RiveRuntime 中。
📌 AsyncShared.swift
/// 懒加载并缓存一个值。初始化失败后可以重试。
@MainActor
final class AsyncShared<Value> {
private let makeValue: @MainActor () async throws -> Value
private var cachedValue: Value?
private var loadingTask: Task<Value, Error>?
init(_ makeValue: @escaping @MainActor () async throws -> Value) {
self.makeValue = makeValue
}
func value() async throws -> Value {
if let cachedValue {
return cachedValue
}
let task = loadingTask ?? Task {
defer { loadingTask = nil }
let value = try await makeValue()
cachedValue = value
return value
}
loadingTask = task
return try await task.value
}
}
📌 AsyncShared+Worker.swift
import RiveRuntime
extension AsyncShared where Value == Worker {
convenience init(configuration: Worker.Configuration = .init()) {
self.init {
try await Worker(configuration: configuration)
}
}
}
extension Worker {
@MainActor
private static let sharedWorker = AsyncShared<Worker>()
@MainActor
private static let sharedGPUCanvasWorker = AsyncShared<Worker>(
configuration: .init(enableGPUCanvas: true)
)
@MainActor
static func shared() async throws -> Worker {
try await sharedWorker.value()
}
@MainActor
static func sharedGPUCanvas() async throws -> Worker {
try await sharedGPUCanvasWorker.value()
}
}
let worker = try await Worker.shared()
let file = try await File(source: ..., worker: worker)
let rive = try await Rive(file: file)
共享功能
共享 API
主要的共享 API 领域是通过 RiveFont.fallbackFontsCallback 实现的回退字体。
有关回退字体类型和行为,请参见回退字体。
从 RiveViewModel 到 Rive
使用以下部分作为迁移映射:从磁盘加载文件、创建 Rive 视图和数据绑定。 旧版 RiveViewModel 在这些领域的流程变为显式的 File + Rive 设置,然后是新运行时中的 RiveUIView/SwiftUI 表示类型。
从磁盘加载文件
旧版运行时
// 缓存此文件以便重用
let file = try RiveFile(name: "my_rive_file")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model)
当前运行时
let worker = try await Worker.shared()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let rive = try await Rive(file: file)
从 URL 加载文件
旧版运行时
let webURL = URL(string: "https://example.com/my_rive_file.riv")!
let file = RiveFile(httpUrl: webURL, loadCdn: false, with: self)
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model)
当前运行时
let worker = try await Worker.shared()
let webURL = URL(string: "https://example.com/my_rive_file.riv")!
let file = try await File(
source: .url(webURL),
worker: worker
)
let rive = try await Rive(file: file)
从数据(字节)加载文件
旧版运行时
let data: Data = ...
let file = try RiveFile(data: data, loadCdn: false)
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model)
当前运行时
当你的 .riv 字节已在内存中可用时,使用 .data 文件源。
let worker = try await Worker.shared()
let data: Data = ...
let file = try await File(source: .data(data), worker: worker)
let rive = try await Rive(file: file)
跟踪加载和错误状态
对于常见的设置操作(加载文件、创建画板、创建状态机),迁移模式是:
- 旧版运行时:许多查找 API 返回 optional(失败时返回
nil),因此使用guard let/ 回退逻辑处理。 - 新运行时:API 抛出错误,因此使用
do/catch。
旧版运行时
旧版 API 通常在查找/创建类型的操作中返回 nil:
let file = try RiveFile(name: "my_rive_file")
guard let artboard = file.artboard() else {
// 处理缺少画板的情况
return
}
guard let stateMachine = artboard.defaultStateMachine() else {
// 处理缺少状态机的情况
return
}
当前运行时
新的运行时 API 抛出错误,因此失败处理转移到 do/catch:
do {
let worker = try await Worker.shared()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let artboard = try await file.createArtboard("My Artboard")
let stateMachine = try await artboard.createStateMachine("My State Machine")
let rive = try await Rive(file: file, artboard: artboard, stateMachine: stateMachine)
} catch {
// 处理文件/画板/状态机设置错误
}
此模式同样适用于 UIKit 和 SwiftUI。关键的迁移变化是旧版中的可选值解包 vs 新运行时中的错误捕获。
选择画板和状态机
有关更多信息,请参见画板文档获取更多详细信息。
有关更多信息,请参见状态机文档获取更多详细信息。
旧版运行时
let file = try RiveFile(name: "my_rive_file")
let model = RiveModel(riveFile: file)
model.setArtboard("My Artboard")
model.setStateMachine("My State Machine")
当前运行时
let worker = try await Worker.shared()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let artboard = try await file.createArtboard("My Artboard")
let stateMachine = try await artboard.createStateMachine("My State Machine")
let rive = try await Rive(file: file, artboard: artboard, stateMachine: stateMachine)
创建 Rive 视图
旧版运行时
let viewModel = RiveViewModel(...)
// UIKit
let riveView = viewModel.createRiveView()
// SwiftUI
var body: some View {
viewModel.view()
}
当前运行时
let worker = try await Worker.shared()
let file = try await File(source: ..., worker: worker)
let rive = try await Rive(file: file)
// UIKit(同步对象已可用)
let riveView = RiveUIView(rive: rive)
// UIKit(异步加载 )
let riveView = RiveUIView({
let worker = try await Worker.shared()
let file = try await File(source: ..., worker: worker)
return try await Rive(file: file)
})
// SwiftUI
var body: some View {
RiveUIViewRepresentable(rive)
}
// SwiftUI(异步)
var body: some View {
AsyncRiveUIViewRepresentable {
let worker = try await Worker.shared()
let file = try await File(source: ..., worker: worker)
return try await Rive(file: file)
}
}
设置适配和对齐
有关更多信息,请参见布局文档获取所有适配和对齐选项。
旧版运行时
let viewModel = RiveViewModel(
fileName: "my_rive_file",
fit: .contain,
alignment: .center
)
// 在运行时更新
viewModel.fit = .fitWidth
viewModel.alignment = .topCenter
在旧版运行时中使用画板布局尺寸:
let viewModel = RiveViewModel(fileName: "my_rive_file")
viewModel.fit = .layout
viewModel.layoutScaleFactor = RiveViewModel.layoutScaleFactorAutomatic // 默认行为
// 或显式设置缩放因子
viewModel.layoutScaleFactor = 2.0
当前运行时
let worker = try await Worker.shared()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
var rive = try await Rive(file: file, fit: .contain(alignment: .center))
// 在运行时更新
rive.fit = .fitWidth(alignment: .topCenter)
在新运行时中使用画板布局尺寸:
let worker = try await Worker.shared()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
var rive = try await Rive(file: file, fit: .layout(scaleFactor: .automatic))
// 或显式设置缩放因子
rive.fit = .layout(scaleFactor: .explicit(2.0))
默认布局缩放因子
使用布局适配时,两个运行时都支持自动和显式缩放因子。
旧版运行时
使用 RiveViewModel.layoutScaleFactorAutomatic(默认)或在 layoutScaleFactor 上设置显式数值。
当前运行时
在 Rive.fit 上使用 .layout(scaleFactor: .automatic) 或 .layout(scaleFactor: .explicit(...))。
视图模型
有关更多信息,请参见数据绑定文档获取完整的视图模型实例 API。
旧版运行时
在旧版中,视图模型从 RiveFile 查询,然后从该查询的视图模型创建实例。
let riveViewModel = RiveViewModel(...)
let file = riveViewModel.riveModel!.riveFile
guard let viewModel = file.viewModelNamed("My View Model") else {
return
}
// 从查询的视图模型创建空白/默认/命名实例
let blankInstance = viewModel.createInstance()
let defaultInstance = viewModel.createDefaultInstance()
let namedInstance = viewModel.createInstance(fromName: "My Instance")
当前运行时
在新运行时中,视图模型表示为直接传递给 createViewModelInstance 的源元数据。
let worker = try await Worker.shared()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
// 通过显式视图模型名称
let blankInstance = try await file.createViewModelInstance(
from: .blank(from: .name("My View Model"))
)
let defaultInstance = try await file.createViewModelInstance(
from: .name("My View Model")
)
let namedInstance = try await file.createViewModelInstance(
from: .name("My Instance", from: .name("My View Model"))
)
你也可以从画板默认值获取视图模型:
let artboard = try await file.createArtboard()
let defaultFromArtboard = try await file.createViewModelInstance(
from: .viewModelDefault(from: .artboardDefault(artboard))
)
视图模型实例属性
旧版运行时
旧版数据绑定实例暴露你从实例查询的类型化属性对象。
let riveViewModel = RiveViewModel(...)
var instance: RiveDataBindingViewModel.Instance!
riveViewModel.riveModel?.enableAutoBind { boundInstance in
instance = boundInstance
}
guard let stringProperty = instance.stringProperty(fromPath: "path/to/string") else {
return
}
// 设置
stringProperty.value = "Hello, Rive"
// 获取
let currentValue = stringProperty.value
当前运行时
新的运行时使用类型化路径描述符,配合 ViewModelInstance 上的方法进行设置/获取/观察。
let worker = try await Worker.shared()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let viewModelInstance = try await file.createViewModelInstance(
from: .name("My View Model")
)
let stringProperty = StringProperty(path: "path/to/string")
// 设置
viewModelInstance.setValue(of: stringProperty, to: "Hello, Rive")
// 获取当前值
let currentValue = try await viewModelInstance.value(of: stringProperty)
// 随时间观察变化
let valueStream = viewModelInstance.valueStream(of: stringProperty)
for try await updatedValue in valueStream {
print(updatedValue)
}
可绑定画板
旧版运行时
旧版画板属性绑定使用可绑定画板包装器类型。
let instance: RiveDataBindingViewModel.Instance = ...
guard let artboardProperty = instance.artboardProperty(fromPath: "path/to/artboard") else {
return
}
let components = try RiveFile(name: "component_library")
guard let bindableArtboard = components.bindableArtboard(withName: "My Artboard") else {
return
}
artboardProperty.setValue(bindableArtboard)
当前运行时
新的运行时通过类型化属性描述符和实例上的 setValue 绑定画板。
let worker = try await Worker.shared()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let viewModelInstance = try await file.createViewModelInstance(.viewModelDefault(from: .name("My View Model")))
let artboardProperty = ArtboardProperty(path: "path/to/artboard")
let artboard = try await file.createArtboard("My Artboard")
viewModelInstance.setValue(of: artboardProperty, to: artboard)
数据绑定
有关更多信息,请参见数据绑定文档获取完整 API 覆盖。
旧版运行时
let file = try RiveFile(...)
let model = RiveModel(riveFile: file)
model.enableAutoBind { instance in
self.viewModelInstance = instance
instance.stringProperty(fromPath: "...").value = "Hello, Rive"
}
你也可以手动绑定特定实例:
let file = try RiveFile(...)
guard let artboard = file.artboard() else { return }
guard let stateMachine = artboard.defaultStateMachine() else { return }
guard let viewModel = file.defaultViewModel(for: artboard) else { return }
guard let instance = viewModel.createDefaultInstance() else { return }
stateMachine.bindViewModelInstance(instance) // 绑定成功
当前运行时
当编辑器中创作的默认值已足够、且你不需要访问它们的属性时,使用 Rive(file:)。若需要编程访问,请用显式创建实例替代旧版 auto-bind 回调,并将实例保留在应用的模型中:
let worker = try await Worker.shared()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let artboard = try await file.createArtboard()
let instance = try await file.createViewModelInstance(
.viewModelDefault(from: .artboardDefault(artboard))
)
let stateMachine = try await artboard.createStateMachine(binding: instance)
let rive = try await Rive(file: file, artboard: artboard, stateMachine: stateMachine)
instance.setValue(of: StringProperty(path: "path/to/string"), to: "Hello, Rive")
请在 @MainActor 的 async 上下文中创建这些组件。保留实例引用以便稍后访问和观察属性。全局绑定见全局视图模型。
更新现有并发运行时代码(Updating existing concurrency runtime code)
全局视图模型更新也改变了新并发运行时中的 API:
| 先前 API | 替代方案 |
|---|---|
Rive(file: file, dataBind: .auto) | 使用 Rive(file: file) 绑定创作默认值,或显式创建实例以访问属性 |
Rive(file: file, dataBind: .instance(instance)) | artboard.createStateMachine(binding: instance),然后将画板和状态机传给 Rive |
Rive(..., dataBind: .none) | 将已配置的画板和状态机传给 Rive,不使用 dataBind |
rive.viewModelInstance | 保留你创建并绑定的实例 |
stateMachine.bindViewModelInstance(instance) | try await stateMachine.bindViewModelInstances(main: instance) |
用于重绘的 instance.dirtyStream() | RiveController 自动观察绑定实例 |
像 Rive(file:) 这样的调用可能仍能编译,但行为已改变:新的初始化器会使已弃用的 viewModelInstance getter 返回 nil。Rive 和 StateMachine 都不会暴 露自动绑定实例的 getter。如果你的代码访问该 getter,仅移除 dataBind: 是不够的。
Rive(file:) 和 Rive(file:artboard:) 会创建状态机并绑定创作默认值。Rive(file:artboard:stateMachine:) 使用所提供的状态机,不会重新绑定。创建状态机时已经会绑定可用的默认值,因此省略 dataBind 并不能禁用这次初始绑定。同样,已弃用的 .none 只在旧版初始化器中跳过绑定;它不会撤销已有绑定。
若需要临时兼容桥接,请显式选择已弃用的初始化器:
let rive = try await Rive(file: file, dataBind: .auto)
let instance = rive.viewModelInstance // 旧版创建的主实例(若可用)。
DataBind、此初始化器和该 getter 均已弃用。该 getter 只是初始化时的快照:之后重新绑定不会更新它。新代码请优先使用上面的显式设置。
为应用设置实例(Setting up instances for your app)
创建所需实例、绑定它们,并把引用保存在应用的模型中。此示例使用默认主实例,以及全局 "Theme" 视图模型中的 "Light" 实例。在 @MainActor 的 async 上下文中运行设置,所有组件使用同一文件和 worker。
let artboard = try await file.createArtboard()
let main = try await file.createViewModelInstance(
.viewModelDefault(from: .artboardDefault(artboard))
)
let theme = try await file.createViewModelInstance(
.name("Light", from: .name("Theme"))
)
let stateMachine = try await artboard.createStateMachine(binding: main) {
("Theme", theme)
}
let rive = try await Rive(file: file, artboard: artboard, stateMachine: stateMachine)
保留 rive、main 和 theme。在呈现视图之前设置任何初始属性值。替换绑定和切换主题见全局视图模型。
可选的自动绑定辅助方法
对于默认实例,可在应用中添加此扩展。它会一并返回 Rive 以及主实例和全局实例:
extension Rive {
@MainActor
static func autoBound(
file: File,
artboardName: String? = nil,
stateMachineName: String? = nil
) async throws -> (
rive: Rive,
main: ViewModelInstance,
globals: [String: ViewModelInstance]
) {
let artboard = try await file.createArtboard(artboardName)
let main = try await file.createViewModelInstance(
.viewModelDefault(from: .artboardDefault(artboard))
)
var globals: [String: ViewModelInstance] = [:]
for name in try await file.getGlobalViewModelNames() {
globals[name] = try await file.createViewModelInstance(
.viewModelDefault(from: .name(name))
)
}
let stateMachine = try await artboard.createStateMachine(stateMachineName, binding: main) {
for (name, instance) in globals {
(name, instance)
}
}
let rive = try await Rive(file: file, artboard: artboard, stateMachine: stateMachine)
return (rive, main, globals)
}
}
这是应用自有的辅助方法,不是运行时 API。它要求存在默认主实例以及每个全局的默认实例;缺少默认值会抛出错误。没有全局时,字典为空。
从 @MainActor 的 async 上下文调用它,并保留 autoBound:
let autoBound = try await Rive.autoBound(file: file)
autoBound.main.setValue(of: StringProperty(path: "title"), to: "Welcome")
let theme = autoBound.globals["Theme"]
let riveView = RiveUIView(rive: autoBound.rive)
如果你稍后替换绑定,请在绑定成功后更新你存储的实例引用。